package kernel;

import rescuecore2.connection.Connection;
import rescuecore2.connection.ConnectionListener;
import rescuecore2.messages.Message;
import rescuecore2.messages.Command;
import rescuecore2.messages.control.SKUpdate;
import rescuecore2.messages.control.KSUpdate;
import rescuecore2.messages.control.KSCommands;
import rescuecore2.messages.control.EntityIDRequest;
import rescuecore2.messages.control.EntityIDResponse;
import rescuecore2.worldmodel.ChangeSet;
import rescuecore2.worldmodel.EntityID;
import rescuecore2.log.Logger;

import java.util.Collection;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;

/**
   This class is the kernel interface to a simulator.
 */
public class SimulatorProxy extends AbstractKernelComponent {
    private Map<Integer, ChangeSet> updates;
    private int id;
    private EntityIDGenerator idGenerator;

    /**
       Construct a new simulator.
       @param name The name of the simulator.
       @param id The ID of the simulator.
       @param c The connection this simulator is using.
    */
    public SimulatorProxy(String name, int id, Connection c) {
        super(name, c);
        this.id = id;
        updates = new HashMap<Integer, ChangeSet>();
        c.addConnectionListener(new SimulatorConnectionListener());
    }

    /**
       Get updates from this simulator. This method may block until updates are available.
       @param time The timestep to get updates for.
       @return A ChangeSet representing the updates from this simulator.
       @throws InterruptedException If this thread is interrupted while waiting for updates.
    */
    public ChangeSet getUpdates(int time) throws InterruptedException {
        ChangeSet result = null;
        synchronized (updates) {
            while (result == null) {
                result = updates.get(time);
                if (result == null) {
                    updates.wait(1000);
                }
            }
        }
        return result;
    }

    /**
       Send an update message to this simulator.
       @param time The simulation time.
       @param update The updated entities.
    */
    public void sendUpdate(int time, ChangeSet update) {
        send(new KSUpdate(id, time, update));
    }

    /**
       Send a set of agent commands to this simulator.
       @param time The current time.
       @param commands The agent commands to send.
    */
    public void sendAgentCommands(int time, Collection<? extends Command> commands) {
        send(new KSCommands(id, time, commands));
    }

    @Override
    public String toString() {
        return getName() + " (" + id + "): " + getConnection().toString();
    }

    /**
       Set the EntityIDGenerator.
       @param generator The new EntityIDGenerator.
    */
    public void setEntityIDGenerator(EntityIDGenerator generator) {
        idGenerator = generator;
    }

    /**
       Register an update from the simulator.
       @param time The timestep of the update.
       @param changes The set of changes.
    */
    protected void updateReceived(int time, ChangeSet changes) {
        synchronized (updates) {
            ChangeSet c = updates.get(time);
            if (c == null) {
                c = new ChangeSet();
                updates.put(time, c);
            }
            c.merge(changes);
            updates.notifyAll();
        }
    }

    private class SimulatorConnectionListener implements ConnectionListener {
        @Override
        public void messageReceived(Connection connection, Message msg) {
            Logger.pushLogContext(Kernel.KERNEL_LOG_CONTEXT);
            try {
                if (msg instanceof SKUpdate) {
                    SKUpdate update = (SKUpdate)msg;
                    if (update.getSimulatorID() == id) {
                        updateReceived(update.getTime(), update.getChangeSet());
                    }
                }
                if (msg instanceof EntityIDRequest) {
                    EntityIDRequest req = (EntityIDRequest)msg;
                    Logger.debug("Simulator proxy " + id + " received entity ID request: " + msg);
                    if (req.getSimulatorID() == id) {
                        int requestID = req.getRequestID();
                        int count = req.getCount();
                        List<EntityID> result = new ArrayList<EntityID>(count);
                        for (int i = 0; i < count; ++i) {
                            result.add(idGenerator.generateID());
                        }
                        Logger.debug("Simulator proxy " + id + " sending new IDs: " + result);
                        send(new EntityIDResponse(id, requestID, result));
                    }
                }
            }
            finally {
                Logger.popLogContext();
            }
        }
    }
}
